Список Задач + SQLite + Interface + DI
➡️Ссылка на репозиторий с кодом этого урока
Подход 2: Через ORM
Он более читаемый, менее подвержен ошибкам в синтаксисе SQL и является предпочтительным для стандартных CRUD-операций (Create, Read, Update, Delete.
Пакет sqflite сам формирует SQL-запрос из предоставленных данных.
ORM - Объектная обёртка над чистым SQL, нужна для удобства и надёжности при составлении и использовании запросов.

Файл lib/services/sqlite_orm_service.dart
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import '../services/storage_interface.dart';
import '../models/task.dart';
class SqliteORMService implements IStorageService {
// Приватная переменная для хранения экземпляра БД
Database? _database;
static const _dbName = 'tasks_database.db'; // Название файла с бд
static const _tasksTableName = 'tasks'; // 1 таблица для списка задач
static const _settingsTableName = 'settings'; // 2 таблица для настроек
// Геттер для получения экземпляра БД.
// Если он еще не создан, инициализирует его.
Future<Database> get database async {
if (_database != null) {
return _database!;
}
_database = await _initDB();
return _database!;
}
/// Инициализация базы данных
Future<Database> _initDB() async {
// Получаем путь к каталогу для баз данных по умолчанию
// Для Android это data/data/<package_name>/databases
final dbPath = await getDatabasesPath();
// Соединяем путь и имя файла БД
final path = join(dbPath, _dbName);
return await openDatabase(
path,
version: 1,
// onCreate выполняется 1 раз
onCreate: (db, version) async {
// Выполняем SQL-запрос для создания таблицы
await db.execute('''
CREATE TABLE $_tasksTableName(
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
isDone INTEGER NOT NULL
)
''');
// Выполняем SQL-запрос для создания таблицы
await db.execute('''
CREATE TABLE $_settingsTableName(
key TEXT PRIMARY KEY,
value INTEGER
)''');
},
);
}
/// Создание новой задачи
@override
Future<Task> createTask(String text) async {
final db = await database;
final newId = await db.insert(
_tasksTableName,
{'text': text, 'isDone': 0},
);
// Создаём и возвращаем объект Task с ID из базы данных.
return Task(id: newId, text: text, isDone: false);
}
/// Получение всех задач
@override
Future<List<Task>> getAllTasks() async {
final db = await database;
final List<Map<String, dynamic>> allTasks = await db.query(_tasksTableName);
// Преобразуем список Map в список объектов Task и возвращаем его
return List.generate(allTasks.length, (i) {
return Task(
id: allTasks[i]['id'],
text: allTasks[i]['text'],
isDone: allTasks[i]['isDone'] == 1,
);
});
}
/// Обновление задачи
@override
Future<void> updateTask(Task task) async {
final db = await database;
await db.update(
_tasksTableName,
{'text': task.text, 'isDone': task.isDone ? 1 : 0},
where: 'id = ?',
whereArgs: [task.id],
);
}
/// Удаление задачи
@override
Future<void> deleteTask(int id) async {
final db = await database;
await db.delete(_tasksTableName, where: 'id = ?', whereArgs: [id]);
}
/// Получение цветовой темы
@override
Future<bool> getThemeMode() async {
final db = await database;
final List<Map<String, dynamic>> settings = await db.query(
_settingsTableName,
columns: ['value'],
where: 'key = ?',
whereArgs: ['isDarkMode'],
);
if (settings.isNotEmpty) {
return settings.first['value'] == 1;
}
return false;
}
/// Сохранение цветовой темы
@override
Future<void> saveThemeMode(bool isDarkMode) async {
final db = await database;
await db.insert(
_settingsTableName,
{'key': 'isDarkMode', 'value': isDarkMode ? 1 : 0},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}